package org.rms.cayenne.helper;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.apache.cayenne.DataObject;
import org.apache.cayenne.exp.ExpressionFactory;
import org.apache.cayenne.map.ObjAttribute;
import org.apache.cayenne.map.ObjEntity;
import org.apache.cayenne.map.ObjRelationship;
import org.apache.cayenne.query.Query;
import org.apache.cayenne.query.SelectQuery;
import org.apache.cocoon.processing.ProcessInfoProvider;
import org.apache.cocoon.spring.configurator.WebAppContextUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rms.model.access.Persistable;
import org.rms.model.forms.FormHelper;


public class CayenneFormHelper implements FormHelper {

    public static final Log logger = LogFactory.getLog(CayenneFormHelper.class);

    // dates are parsed starting with the most precise format
    protected static final String[] DATE_PATTERNS = { "dd/MM/yyyy HH:mm:ss", "dd/MM/yyyy HH:mm",
            "dd/MM/yyyy" };

    public void saveFromForm(Persistable persistable) {
        saveFromForm(persistable, Collections.<String> emptySet());
    }

    public void saveFromForm(Persistable persistable, Collection<String> excludedProperties) {
        final DataObject object = (DataObject) persistable;
        final ProcessInfoProvider process = (ProcessInfoProvider) WebAppContextUtils
                .getCurrentWebApplicationContext().getBean(ProcessInfoProvider.ROLE);
        final HttpServletRequest request = process.getRequest();

        final ObjEntity objEntity = object.getObjectContext().getEntityResolver().getObjEntity(
                object.getClass().getSimpleName());
        for (final ObjAttribute attr : objEntity.getAttributes()) {
            final String name = attr.getName();
            if (!excludedProperties.contains(name)) {
                final String requestParam = request.getParameter(name);
                final Class<?> klass = attr.getJavaClass();
                if (requestParam != null) {
                    final String value = requestParam.trim();
                    if (klass == String.class) {
                        object.writeProperty(name, requestParam);
                    } else if (klass == Integer.class) {
                        object.writeProperty(name, value.equals("") ? null : Integer
                                .parseInt(value));
                    } else if (klass == Double.class) {
                        object.writeProperty(name, value.equals("") ? null : Double
                                .parseDouble(value));
                    } else if (klass == Date.class) {
                        object.writeProperty(name, value.equals("") ? null : parseDate(value));
                    } else if (klass == Boolean.class) {
                        object.writeProperty(name, value.equalsIgnoreCase("true"));
                    }
                }
            }
        }
        for (final ObjRelationship rel : objEntity.getRelationships()) {
            final String name = rel.getName();
            if (!excludedProperties.contains(name)) {
                final String[] requestParam = request.getParameterValues(name);
                logger.debug("Parameters: " + name + " = [ " + StringUtils.join(requestParam, ", ")
                        + " ]");
                if (requestParam != null) {
                    final ObjEntity target = (ObjEntity) rel.getTargetEntity();
                    final Collection<String> names = target.getPrimaryKeyNames();
                    if (names.size() == 1) {
                        final String key = names.iterator().next();
                        final Query query = new SelectQuery(rel.getTargetEntityName(),
                                ExpressionFactory.inExp(key, Arrays.asList(requestParam)));
                        final List<DataObject> results = object.getObjectContext().performQuery(
                                query);
                        logger.debug("Results: [ " + StringUtils.join(results, ", ") + " ]");
                        if (rel.isToMany()) {
                            final List<DataObject> oldObjects = new ArrayList<DataObject>(
                                    (List<DataObject>) object.readProperty(name));
                            for (final DataObject obj : oldObjects) {
                                if (!results.contains(obj)) {
                                    object.removeToManyTarget(name, obj, true);
                                }
                            }
                            for (final DataObject obj : results) {
                                if (!oldObjects.contains(obj)) {
                                    object.addToManyTarget(name, obj, true);
                                }
                            }
                        } else {
                            if (results.size() == 1) {
                                object.setToOneTarget(name, results.get(0), true);
                            }
                        }
                    }
                }
            }
        }
    }

    protected Date parseDate(final String requestParam) {
        Date date = null;
        int i = 0;
        while (date == null && i < DATE_PATTERNS.length) {
            try {
                date = new SimpleDateFormat(DATE_PATTERNS[i]).parse(requestParam);
            } catch (ParseException ignore) {
            }
            i++;
        }
        if (date == null) {
            throw new IllegalStateException("Could not parse date '" + requestParam + "'.");
        }
        return date;
    }
}
